package org.geektimes.projects.user.repository;

import org.apache.commons.lang.ClassUtils;
import org.geektimes.function.ThrowableFunction;
import org.geektimes.projects.user.domain.User;
import org.geektimes.projects.user.sql.DBConnectionManager;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.*;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.logging.Logger;

import static org.apache.commons.lang.ClassUtils.wrapperToPrimitive;

public class DatabaseUserRepository implements UserRepository {

    private Connection connection;

    private static Map<Class,String> resultSetMethodMapping=new HashMap<>();

    private static Map<Class,String> preparedStatementMethodMapping=new HashMap();


    public DatabaseUserRepository(Connection connection) {
        this.connection = connection;
    }

    public static final String INSERT_USER_SQL = "INSERT INTO users(name,password,email,phoneNumber) VALUES(?,?,?,?)";

    @Override
    public boolean save(User user) throws SQLException {
        PreparedStatement preparedStatement = connection.prepareStatement(INSERT_USER_SQL);
        preparedStatement.setString(1,user.getName());
        preparedStatement.setString(2,user.getPassword());
        preparedStatement.setString(3,user.getEmail());
        preparedStatement.setString(4,user.getPhoneNumber());
        boolean flag=preparedStatement.execute();
        return flag;
    }

    @Override
    public boolean deleteById(Long userId) {
        return false;
    }

    @Override
    public boolean update(User user) {
        return false;
    }

    @Override
    public User getById(Long userId) {
        return null;
    }

    @Override
    public User getByNameAndPassword(String userName, String password) {
        return null;
    }

    @Override
    public Collection<User> getAll() {
        String sql="SELECT * FROM USERS";
        Collection<User> resultSet = executeQuery(sql, result->{
            List<User> userList = new ArrayList<>();
            BeanInfo beanInfo = Introspector.getBeanInfo(User.class, Object.class);
            while (result.next()) {
                User user = new User();
                for (PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors()) {
                    Method writeMethod = descriptor.getWriteMethod();
                    String name = descriptor.getName();
                    Class<?> type = descriptor.getPropertyType();
                    Method method = ResultSet.class.getMethod(resultSetMethodMapping.get(type),String.class);
                    writeMethod.invoke(user,method.invoke(result,name));
                }
                userList.add(user);
            }
            return userList;

        });



        return resultSet;
    }

    private  <T>T executeQuery(String sql, ThrowableFunction<ResultSet,T> function , Object... args)  {
        try {
            PreparedStatement preparedStatement=connection.prepareStatement(sql);
            for(int i=0,j=1;i<args.length;i++,j++){
                Class<?> wrapperClass= ClassUtils.wrapperToPrimitive(args[i].getClass());
                //String no primitive
                if(wrapperClass==null){
                    wrapperClass=args[i].getClass();
                }
                Method method = preparedStatement.getClass()
                        .getMethod(preparedStatementMethodMapping.get(args[i].getClass()), int.class, wrapperClass);
                method.invoke(preparedStatement,j,args[i]);
            }
            T apply = function.apply(preparedStatement.executeQuery());
            return apply;
        }catch (Throwable throwable){
        }

        return null;

    }

    static {
        resultSetMethodMapping.put(String.class, "getString");
        resultSetMethodMapping.put(int.class, "getInt");
        resultSetMethodMapping.put(Long.class, "getLong");


        preparedStatementMethodMapping.put(Long.class, "setLong"); // long
        preparedStatementMethodMapping.put(String.class, "setString"); //
    }

    public static void main(String[] args) throws Exception {
        DatabaseUserRepository databaseUserRepository = new DatabaseUserRepository(DBConnectionManager.getConnection());
        databaseUserRepository.getAll().forEach(System.out::println);
    }
}
